#include "SwScheduler.h"

#include <cstdio>
#include <cstring>

using namespace Idis;

#define trace printf

const uint32 SwScheduler::BIT_COUNT = 8;
const uint32 SwScheduler::MAX_PENALTY = 16;
const uint32 SwScheduler::MAX_WAITING_COUNT = 61;

SwScheduler::SwScheduler()
    : _ntsc(true), _imagePerField(32)
    , _fieldCountForSecond(60)
{
    ::memset(_scheduleList, 0, sizeof(_scheduleList));
}

void SwScheduler::init(int32 videoType)
{
    _imagePerField = 32; // 2: 120 ips, 4: 240 ips, 8: 480 ips
    _ntsc = (VIDEO_NTSC == videoType);
    _fieldCountForSecond = (_ntsc ? 60 : 50);

    for (int32 i = 0; i < MCP_CODEC_SLOT_COUNT; i++) {
        _scheduleList[i].enable = false;
        _scheduleList[i].channel = 0;
        _scheduleList[i].resolution = MAX_RESOLUTION_COUNT;
        _scheduleList[i].interval = 0;
        _scheduleList[i].increment = 0;
        _scheduleList[i].penalty = 0;
        _scheduleList[i].requestedKeyFrame = false;
        _scheduleList[i].maxInterval = 0;
        _estimatedIps[i] = 0;
    }
}

void SwScheduler::clear()
{
    for (int32 i = 0; i < MCP_CODEC_SLOT_COUNT; i++) {
        _estimatedIps[i] = 0;
        _scheduleList[i].timeAfterSchedule = _scheduleList[i].interval - _scheduleList[i].increment;
        _scheduleList[i].waitingCount = 0;
    }
}
    
void SwScheduler::applySituation(int32 channel, uint32 resolution, float ips, bool enable, bool keyFrame)
{
    const int32 MAX_IPS = (_ntsc ? 30 : 25);
    float ipsVal = MAX_IPS / ips + 1, maxVal = 0;

    if (ipsVal <= 1) {
        maxVal = 2;
    }
    else if (ipsVal >= 2 && ipsVal < 4) {
        maxVal = 4;
    }
    else if (ipsVal >= 4 && ipsVal < 8) {
        maxVal = 8;
    }
    else if (ipsVal >= 8 && ipsVal < 16) {
        maxVal = 16;
    }
    else {
        maxVal = 32;
    }

    const float SCALE_FACTOR = (((float)(1 << BIT_COUNT)) / maxVal);

    ScheduleItem& info = _scheduleList[channel];

    info.enable = enable;
    info.channel = channel;
    info.resolution = resolution;
    info.requestedKeyFrame = keyFrame;
    info.interval = (int32)((MAX_IPS * SCALE_FACTOR + ips / 2) / ips);
    info.increment = (int32)(SCALE_FACTOR);
    info.penalty = (int32)((MAX_PENALTY * ips + MAX_IPS - 1) / MAX_IPS);
    info.maxInterval = (info.interval << 1);

#ifdef x_DEBUG
    if (enable) {
        trace("  schedule-thread: <Mcp::%s> ", __FUNCTION__);
        trace("ch:%02d(%s), field:0, res:%d, q:0, ips:%.2f, " \
              "incr:%3d, interval:%3d, penalty:%2d (SW SCHED)\n", \
                    channel + 1, enable ? "ON " : "OFF", \
                    resolution, ips, info.increment, info.interval, \
                    info.penalty);
    }
#endif
}

void SwScheduler::simulate()
{
    ScheduleItem* candidate = 0;
    uint32 fieldCount = 0, i = 0, index = 0;

    clear();

    while (fieldCount < _fieldCountForSecond) {
        if (0 == (fieldCount % 2)) {
            // init aging factors
            for (int32 j = 0; j < MCP_CODEC_SLOT_COUNT; j++) {
                ScheduleItem& info = _scheduleList[j];
                info.timeAfterSchedule += info.increment;
                if (info.timeAfterSchedule > info.maxInterval) {
                    info.timeAfterSchedule = info.maxInterval;
                }
            }

            // compute wait count
            for (int32 j = 0; j < MCP_CODEC_SLOT_COUNT; j++) {
                ScheduleItem& info = _scheduleList[j];
                if (info.timeAfterSchedule >= info.interval) {
                    if (info.waitingCount < MAX_PENALTY) {
                        info.waitingCount += info.penalty;
                        if (info.waitingCount > MAX_PENALTY) {
                            info.waitingCount = MAX_PENALTY;
                        }
                    }
                    else if (info.waitingCount < MAX_WAITING_COUNT) {
                        info.waitingCount += 1;
                    }
                }
            }
        }

        i = 0;
        for (int32 k = 0; k < (int32)_imagePerField; k++) {
            if (i < _imagePerField) {
                // finding candidate
                if (candidate) {
                    index = 0;
                    for (int32 j = 0; j < MCP_CODEC_SLOT_COUNT; j++) {
                        ScheduleItem& info = _scheduleList[j];
                        if (candidate == &info) {
                            index = (j + 1) % MCP_CODEC_SLOT_COUNT;
                            break;
                        }
                    }
                }

                candidate = 0;
                for (int32 j = 0; j < MCP_CODEC_SLOT_COUNT; j++) {
                    ScheduleItem& info = _scheduleList[index];
                    if (canBeScheduled(i, info)) {
                        if (0 == candidate) {
                            candidate = &info;
                        }
                        else if (info.waitingCount > candidate->waitingCount) {
                            candidate = &info;
                        }
                    }
                    index = (index + 1) % MCP_CODEC_SLOT_COUNT;
                }

                if (candidate) { // if there exist candidate
                    capturedImage(*candidate, fieldCount);
                    candidate->timeAfterSchedule -= candidate->interval;
                    candidate->waitingCount = 0;
                    candidate->requestedKeyFrame = false;

                    if (RESOLUTION_VERY_HIGH == candidate->resolution) {
                        i += 4;
                    }
                    else if (RESOLUTION_HIGH == candidate->resolution) {
                        i += 2;
                    }
                    else if (RESOLUTION_STANDARD == candidate->resolution) {
                        i += 1;
                    }
                }
                else {
                    i += 1;
                }
            }
        }

        fieldCount += 1;
    }
}

bool SwScheduler::canBeScheduled(int32 imageCount, const ScheduleItem& info)
{
    if (MAX_RESOLUTION_COUNT == info.resolution) {
        return false;
    }
    if (info.waitingCount == 0) {
        return false;
    }

    if (RESOLUTION_VERY_HIGH == info.resolution && 0 != imageCount % 4) {
        return false;
    }
    else if (RESOLUTION_HIGH == info.resolution && (_imagePerField - 2) < (uint32)imageCount) {
        return false;
    }

    return true;
}

void SwScheduler::capturedImage(ScheduleItem& info, uint32 fieldCount)
{
//#ifdef _DEBUG
    trace("  schedule-thread: <%s> ", __FUNCTION__);
    trace("CAM%02d fieldCount=%2u, timeAfterSchedule=%u, waitingCount=%u\n", info.channel + 1, \
        fieldCount, info.timeAfterSchedule, info.waitingCount);
//#endif
    _estimatedIps[info.channel]++;
}


// EOF


int main(int argc, char* argv[])
{
    SwScheduler s;
    
    s.init(VIDEO_PAL);

    for (int32 i = 0; i < 32; i++) {
        s.applySituation(i, 2, 25.0, true, false);
    }

    /*
    for (int32 i = 0; i < 16; i++) {
        s.applySituation(i + 16, 1, 25.0, true, false);
    }
    */

    s.simulate();

    for (int32 i = 0; i < 32; i++) {
        trace("CAM%02d: %.2f ips\n", i + 1, s.estimatedIps(i));
    }
    
    return 0;    
}





